﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

enum FileType
{ 
	IMAGE,
	VIDEO,
	OTHER
}

namespace TestDataCreator
{
	public class LicenseNumberTester : ILicenseNumberTester
	{
		/// <summary>
		/// A reference data set of image/video paths and license plate numbers
		/// </summary>
		private List<DataItem> Data = null;

		/// <summary>
		/// Here the absolute path to a path where the .CSV file and all the 
		/// images/videos are stored is written
		/// </summary>
		private string CurrentCsvDirectory = string.Empty;

		/// <summary>
		/// A directory from which a data set was created
		/// </summary>
		private string CurrentFilesDirectory = string.Empty;

		/// <summary>
		/// An array of common image file extensions
		/// </summary>
		private string[] imageExtensions = new string[] { ".bmp", ".gif", ".jpg", ".jpeg", ".png" };

		/// <summary>
		/// An array of the most common video file extensions
		/// </summary>
		private string[] videoExtensions = new string[] { ".avi", ".mpg", ".mov", ".mp4", ".flv", ".wmv" }; 

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.CreateDataFromFolder(string, bool)"/>
		/// </summary>
		public bool CreateDataFromFolder(string path, bool includeSubfolders, bool firstRun)
		{
			if (!Directory.Exists(path))
				throw new DirectoryNotFoundException("Specified directory does not exist.");

			if(firstRun)
				this.CurrentFilesDirectory = path;

			try
			{
				if (this.Data == null)
					this.Data = new List<DataItem>();

				var sourceInfo = new DirectoryInfo(path);
				FileInfo[] fileList = sourceInfo.GetFiles();

				foreach (var file in fileList)
				{
					string fileName = Path.GetFileName(file.FullName);
					string fileExtension = file.Extension;

					FileType fileType = CheckExtension(fileExtension);

					if( fileType != FileType.OTHER )
						Data.Add(new DataItem()
						{
							ImagePath = file.FullName,
							LicenseNumber = string.Empty,
							IsVideo = (fileType == FileType.VIDEO) ? true : false
						});
				}

				if (includeSubfolders)
				{
					var childFolderList = sourceInfo.GetDirectories();

					foreach (var folder in childFolderList)
						CreateDataFromFolder(folder.FullName, true, false);
				}
			}
			
			catch //(Exception)
			{
				//throw;
				return false;
			}

			return true;
		}

		/// <summary>
		/// Checks file extension to determine whether a file is video or an image type
		/// </summary>
		/// <param name="fileExtension">File extension to be checked</param>
		/// <returns>Extension type</returns>
		private FileType CheckExtension(string fileExtension)
		{
			foreach (var extension in imageExtensions)
				if (string.Compare(extension.ToLower(), fileExtension.ToLower()) == 0)
					return FileType.IMAGE;
			
			foreach (var extension in videoExtensions)
				if (string.Compare(extension.ToLower(), fileExtension.ToLower()) == 0)
					return FileType.VIDEO;

			return FileType.OTHER;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.WriteData(string, bool)"/>
		/// </summary>
		public bool WriteData(string target, bool copyFiles)
		{
			CurrentCsvDirectory = Path.GetDirectoryName(target);
			
			if (Path.GetExtension(target) == string.Empty)
				target += ".csv";

			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(target));

				using (var streamWriter = new StreamWriter(target, false))
				{
					//streamWriter.WriteLine(copyFiles.ToString());

					foreach (var item in this.Data)
					{
						streamWriter.WriteLine(item.ToString(CurrentFilesDirectory));

						if (copyFiles)
						{
							string from = item.ImagePath;
							string to = item.ImagePath.Replace(this.CurrentFilesDirectory, CurrentCsvDirectory);

							if (!Directory.Exists(Path.GetDirectoryName(to)))
								Directory.CreateDirectory(Path.GetDirectoryName(to));

							File.Copy(from, to, true);
						}
					}
					streamWriter.Close();
				}
			}
			catch //(Exception)
			{
				// throw;
				return false;
			}

			return true;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.GetDataFromFile(string)"/>
		/// </summary>
		public bool GetDataFromFile(string path)
		{
			if (!File.Exists(path))
				throw new FileNotFoundException("Specified file does not exist.");

			this.CurrentCsvDirectory = Path.GetDirectoryName(path);
			this.CurrentFilesDirectory = Path.GetDirectoryName(path);

			try
			{
				this.Data = null;
				this.Data = new List<DataItem>();

				using (var streamReader = new StreamReader(path))
				{
					string line;
					string[] rows;

					// bool relativePaths = Convert.ToBoolean(streamReader.ReadLine());

					while ((line = streamReader.ReadLine()) != null)
					{
						rows = line.Split(';');

						//if(relativePaths) 
						rows[0] = Path.Combine(CurrentCsvDirectory, rows[0]);

						Data.Add(new DataItem(rows));
					}
				}

			}
			catch //(FileLoadException)
			{
				//throw;
				return false;
			}
			return true;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.GetLicenseNumber(int)"/>
		/// </summary>
		public string GetLicenseNumber(int num)
		{
			return this.Data[num].LicenseNumber;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.GetLicenseNumber(string)"/>
		/// </summary>
		public string GetLicenseNumber(string path)
		{
			foreach (var item in this.Data)
			{
				if (string.Compare(path, item.ImagePath) == 0)
				{
					return item.LicenseNumber;
				}
			}
			return string.Empty;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.EditLicenseNumber(int, string)"/>
		/// </summary>
		public void EditLicenseNumber(int num, string newValue)
		{
			this.Data[num].LicenseNumber = newValue;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.EditLicenseNumber(string, string)"/>
		/// </summary>
		public void EditLicenseNumber(string path, string newValue)
		{
			for (int i = 0; i < this.Data.Count; i++)
			{
				if (string.Compare(path, this.Data[i].ImagePath) == 0)
				{
					this.Data[i].LicenseNumber = newValue;
				}
			}
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.EditPath(num, string)"/>
		/// </summary>
		public void EditPath(int num, string newPath)
		{
			this.Data[num].ImagePath = newPath;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.EditPath(string, string)"/>
		/// </summary>
		public void EditPath(string oldPath, string newPath)
		{
			for (int i = 0; i < this.Data.Count; i++)
			{
				if (string.Compare(oldPath, this.Data[i].ImagePath) == 0)
				{
					this.Data[i].ImagePath = newPath;
				}
			}
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.BatchTest(List<DataItem>)"/>
		/// </summary>
		public float BatchTest(List<DataItem> results)
		{
			float percentageCorrect = 0.0f;

			if (this.Data == null || this.Data.Count == 0)
				throw new MemberAccessException("Reference data collection not set.");

			if (results == null || results.Count == 0 || results.Count != this.Data.Count)
				throw new ArgumentException("Invalid argument: must be not NULL and the same size as reference data!");

			int count = this.Data.Count;
			int amountOfCorrect = 0;

			for (int i = 0; i < count; i++)
				if (string.Compare(this.Data[i].LicenseNumber, results[i].LicenseNumber) == 0)
					amountOfCorrect++;

			percentageCorrect = ((float)amountOfCorrect) / ((float)count);

			return percentageCorrect;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.BatchTest(List<string>, List<string>)"/>
		/// </summary>
		public float BatchTest(List<string> paths, List<string> licenseNumbers)
		{
			if (paths.Count != licenseNumbers.Count)
				throw new ArgumentException("Invalid arguments: should be the same size!");

			List<DataItem> results = new List<DataItem>();

			for (int i = 0; i < paths.Count; i++)
			{
				results.Add(new DataItem()
					{
						ImagePath = paths[i],
						LicenseNumber = licenseNumbers[i],
						IsVideo = (CheckExtension(Path.GetExtension(paths[i])) == FileType.VIDEO) ? true : false
					});
			}

			return this.BatchTest(results);
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.get(List<DataItem>)"/>
		/// </summary>
		public List<DataItem> Clone()
		{
			List<DataItem> newList = new List<DataItem>(this.Data.Count);

			this.Data.ForEach((item) =>
				{
					newList.Add((DataItem)item.Clone());
				});

			return newList;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.SetData(List<DataItem>)"/>
		/// </summary>
		public void SetData(List<DataItem> newData)
		{
			if (newData.Count == 0 || newData == null)
				throw new ArgumentNullException("Invalid argument: must not be null or empty!");

			this.Data = null;
			this.Data = newData;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.SingleTest(DataItem)"/>
		/// </summary>
		public bool SingleTest(DataItem result)
		{
			foreach (var item in this.Data)
			{
				if (string.Compare(item.ImagePath, result.ImagePath) == 0)
				{
					if (string.Compare(item.LicenseNumber, result.LicenseNumber) == 0)
					{
						return true;
					}
					else
					{
						return false;
					}
				}
			}

			throw new Exception("Corresponding image/video not found in reference dataset!");
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.IsVideo(int)"/>
		/// </summary>
		public bool IsVideo(int num)
		{
			return this.Data[num].IsVideo;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.IsVideo(string)"/>
		/// </summary>
		public bool IsVideo(string path)
		{
			foreach (var item in this.Data)
			{
				if (string.Compare(path, item.ImagePath) == 0)
				{
					return item.IsVideo;
				}
			}

			return false;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.GetPathList(bool)"/>
		/// </summary>
		public List<string> GetPathList(bool namesOnly)
		{
			List<string> returns = new List<string>();
			foreach (var item in this.Data)
				returns.Add((namesOnly) ? item.ImagePath.Replace(this.CurrentFilesDirectory + @"\", string.Empty) : item.ImagePath);

			return returns;
		}

		/// <summary>
		/// Implements <see cref="ILicenseNumberTester.GetPathList()"/>
		/// </summary>
		public List<string> GetLicenseNumbers()
		{
			List<string> returns = new List<string>();
			foreach (var item in this.Data)
				returns.Add(item.LicenseNumber);

			return returns;
		}
	}
}
